/**
 * Name:gform.js
 * Author:gene.jiang
 * E-mail:1013195469@qq.com
 * Website:http://www.jianggujin.com/
 * LICENSE:Apache v2 License
 * Description:Form表单
 */
layui.define(['layer', 'element', 'tool', 'form', 'laydate', 'multivalue'], function(exports) {
    var $ = layui.jquery,
        _body_ = $('body'),
        _tool_ = layui.tool,
        _element_ = layui.element,
        _multivalue_ = layui.multivalue,
        _form_ = layui.form,
        device = layui.device(),
        _laydate_ = layui.laydate,
        _unit_ = ['xs', 'sm', 'md', 'lg'],
        _components_ = {};

    /**
     *  创建表单组件
     * @param {String} type 组件类型
     */
    function createComponent(type) {
        var _const = _components_[type];
        if(typeof _const === 'function') {
            return new _const();
        } else {
            layui.hint().error('Unknown type: ' + type);
            return false;
        }
    }
    /**
     * 处理组件尺寸
     * @param {jQuery} _comp
     * @param {String|JSON|Array} _size
     */
    function processComponentSize(_comp, _size) {
        if(_size === 'block') {
            _comp.addClass("layui-input-block");
        } else if(_size === 'inline') {
            _comp.addClass("layui-input-inline");
        } else if($.isArray(_size)) {
            $.each(_size, function(i, v) {
                _comp.addClass('layui-col-' + v);
            });
        } else if(_size && /^1[0-2]|[1-9]$/.test(_size)) {
            $.each(_unit_, function(i, v) {
                _comp.addClass('layui-col-' + v + _size);
            });
        }
    }
    //=================表单组件==================
    /**
     * 抽象组件，组件都应该继承该组件
     */
    var AbstractComponent = function() {};
    /**
     * 初始化
     * @param {Object} options
     */
    AbstractComponent.prototype.__init = function(options, __form, __row) {
        if(this._initialized) {
            layui.hint().error('Component已经初始化！');
            return;
        }
        options = options || {};
        this.__initComponent(options, __form, __row);
        this._initialized = true;
    };
    /**
     * 初始化组件
     * @param {Object} options
     */
    AbstractComponent.prototype.__initComponent = function(options, __form, __row) {
        this.__initLabelComponent(options, __form, __row);
        this.__initFieldComponent(options, __form, __row);
    };
    var _labelCss = {
        'padding': '9px 15px',
        'height': '38px',
        'line-height': '20px',
        'text-align': 'right'
    };
    AbstractComponent.prototype.__initLabelComponent = function(options, __form, __row) {
        var _label = options.label;
        //存在标签
        if(_label) {
            var _labelComp = this._label = $('<label></label>'),
                _string = typeof _label === 'string',
                _text = _string ? _label : (_label.text || ''),
                _size = _string ? undefined : _label.size;
            if($.isArray(_size)) {
                _labelComp.css(_labelCss);
                $.each(_size, function(i, v) {
                    _labelComp.addClass('layui-col-' + v);
                });
            } else if(_size && /^1[0-2]|[1-9]$/.test(_size)) {
                _labelComp.css(_labelCss);
                $.each(_unit_, function(i, v) {
                    _labelComp.addClass('layui-col-' + v + _size);
                });
            } else {
                _labelComp.addClass('layui-form-label');
            }
            _labelComp.html(_text);
            __row.append(_labelComp);
        }
    };
    AbstractComponent.prototype.__initFieldComponent = function(options, __form, __row) {};
    _components_.abs = AbstractComponent;

    /**
     * 文本输入框组件
     */
    var Text = function() {};
    _tool_.inherit(Text, AbstractComponent);
    $.extend(Text.prototype, {
        __initFieldComponent: function(options, __form, __row) {
            var _that = this,
                _fieldWrapper = $('<div></div>'),
                _field = this._field = $(_that.__fieldTmpl),
                _size = options.size || 'block';
            processComponentSize(_fieldWrapper, _size);

            options.verify && _field.attr('lay-verify', options.verify);
            options.ignore && _field.attr('lay-ignore', '');
            options.verType && _field.attr('lay-verType', options.verType); //tips  alert
            options.name && _field.attr('name', options.name);
            options.placeholder && _field.attr('placeholder', options.placeholder);
            options.attr && _field.attr(options.attr);

            _fieldWrapper.append(_field);
            __row.append(_fieldWrapper);
        },
        __fieldTmpl: '<input type="text" autocomplete="off" class="layui-input">'
    });
    _components_.text = Text;

    /**
     * 文本域组件
     */
    var Textarea = function() {};
    _tool_.inherit(Textarea, Text);
    $.extend(Textarea.prototype, {
        __fieldTmpl: '<textarea class="layui-textarea"></textarea>'
    });
    _components_.textarea = Textarea;

    /**
     * 密码输入框组件
     */
    var Password = function() {};
    _tool_.inherit(Password, Text);
    $.extend(Password.prototype, {
        __fieldTmpl: '<input type="password" autocomplete="off" class="layui-input">'
    });
    _components_.password = _components_.pwd = Password;

    /**
     * 日期时间输入框组件
     */
    var Date = function() {};
    _tool_.inherit(Date, Text);
    $.extend(Date.prototype, {
        __initFieldComponent: function(options, __form, __row) {
            var _that = this,
                args = [].slice.call(arguments, 0);
            Text.prototype.__initFieldComponent.apply(_that, args);

            _laydate_.render($.extend({}, options.data, {
                elem: _that._field[0]
            }));
        },
        __fieldTmpl: '<input type="text" autocomplete="off" class="layui-input">'
    });
    _components_.date = Date;

    /**
     * 日期时间范围输入框组件
     */
    var DateRange = function() {};
    _tool_.inherit(DateRange, AbstractComponent);
    $.extend(DateRange.prototype, {
        __initFieldComponent: function(options, __form, __row) {
            var _that = this,
                _fieldWrapper = $('<div></div>'),
                _size = options.size || 'block',
                _inputTmpl = '<div class="layui-input-inline"><input type="text" autocomplete="off" class="layui-input"></div>';
            var _htmlTmpl = [_inputTmpl, _inputTmpl];

            _fieldWrapper.html(_htmlTmpl.join(''));
            var _field = this._field = _fieldWrapper.find('input');
            _fieldWrapper.children('div').css({
                'margin-right': '0px',
                width: '50%'
            });
            processComponentSize(_fieldWrapper, _size);

            var _optAttrs = ['verify', 'verType', 'name', 'placeholder'],
                _eleAttrs = ['lay-verify', 'lay-verType', 'name', 'placeholder'];
            $.each(_optAttrs, function(i, _item) {
                var _val = options[_item],
                    _name = _eleAttrs[i];
                if(_val) {
                    if($.isArray(_val)) {
                        var len = _val.length;
                        if(len > 1) {
                            _field.each(function(i) {
                                $(this).attr(_name, _val[i]);
                            });
                        } else if(len == 1) {
                            _field.attr(_name, _val[0]);
                        }
                    } else {
                        _field.attr(_name, _val);
                    }
                }
            });
            var _attr = options.attr;
            if(_attr) {
                if($.isArray(_attr)) {
                    var len = _attr.length;
                    if(len > 1) {
                        _field.each(function(i) {
                            $(this).attr(_attr[i]);
                        });
                    } else if(len == 1) {
                        _field.attr(_attr[0]);
                    }
                } else {
                    _field.attr(_attr);
                }
            }
            var _data = options.data;
            if($.isArray(_data)) {
                var len = _data.length;
                if(len > 1) {
                    _field.each(function(i) {
                        var _t = this;
                        _laydate_.render($.extend({}, _data[i], {
                            elem: _t
                        }));
                    });
                } else if(len == 1) {
                    _field.each(function() {
                        var _t = this;
                        _laydate_.render($.extend({}, _data[0], {
                            elem: _t
                        }));
                    });
                }
            } else {
                _field.each(function() {
                    var _t = this;
                    _laydate_.render($.extend({}, _data, {
                        elem: _t
                    }));
                });
            }
            __row.append(_fieldWrapper);
        }
    });
    _components_.dateRange = DateRange;

    /**
     * 选择输入框组件
     */
    var Select = function() {};
    _tool_.inherit(Select, AbstractComponent);
    $.extend(Select.prototype, {
        __initFieldComponent: function(options, __form, __row) {
            var _that = this,
                _fieldWrapper = $('<div></div>'),
                _field = this._field = $('<select></select>'),
                _size = options.size || 'block',
                _text = options.text || 'text',
                _value = options.value || 'value',
                _data = options.data || [];
            processComponentSize(_fieldWrapper, _size);

            options.verify && _field.attr('lay-verify', options.verify);
            options.verType && _field.attr('lay-verType', options.verType); //tips  alert
            options.search && _field.attr('lay-search', '');
            options.ignore && _field.attr('lay-ignore', '');
            options.name && _field.attr('name', options.name);
            options.placeholder && _field.attr('placeholder', options.placeholder);
            options.attr && _field.attr(options.attr);

            if(options.placeholder !== false) {
                $('<option></option>').val('').text(options.placeholder || '').appendTo(_field);
            }
            var _optgroup = null;
            $.each(_data, function(i, item) {
                if(typeof item == 'string') {
                    //已经存在组
                    if(_optgroup) {
                        _field.append(_optgroup);
                    }
                    _optgroup = $('<optgroup></optgroup>');
                    _optgroup.attr("label", item);
                    return;
                } else if(typeof item == 'boolean') {
                    //已经存在组
                    if(_optgroup) {
                        _field.append(_optgroup);
                        _optgroup = null;
                    }
                    return;
                }
                var _options = $('<option></option>').val(item[_value]).text(item[_text]);
                item.disabled && _options.attr('disabled', '');
                item.selected && _options.attr('selected', '');
                (_optgroup || _field).append(_options);
            });
            _optgroup && _field.append(_optgroup);

            _fieldWrapper.append(_field);
            __row.append(_fieldWrapper);
        }
    });
    _components_.select = Select;

    /**
     * 单选框组件 radio，支持多个
     */
    var Radio = function() {};
    _tool_.inherit(Radio, AbstractComponent);
    $.extend(Radio.prototype, {
        __initFieldComponent: function(options, __form, __row) {
            var _that = this,
                _fieldWrapper = $('<div></div>'),
                _size = options.size || 'block',
                _data = options.data || [];
            processComponentSize(_fieldWrapper, _size);

            function buildRadio(opts) {
                var _radio = $(_that.__fieldTmpl);
                opts.verify && _radio.attr('lay-verify', opts.verify);
                options.verType && _field.attr('lay-verType', options.verType); //tips  alert
                options.ignore && _field.attr('lay-ignore', '');
                opts.name && _radio.attr('name', opts.name);
                opts.value && _radio.val(opts.value);
                opts.title && _radio.attr('title', opts.title);
                opts.checked && _radio.attr('checked', '');
                opts.attr && _radio.attr(opts.attr);

                _fieldWrapper.append(_radio);
            }
            //数组，配置多个
            if($.isArray(_data)) {
                $.each(_data, function(i, item) {
                    buildRadio(item)
                });
            } else {
                buildRadio(_data)
            }
            this._field = _fieldWrapper.find('input');
            __row.append(_fieldWrapper);
        },
        __fieldTmpl: '<input type="radio">'
    });
    _components_.radio = Radio;

    /**
     * 复选框组件 radio，支持多个
     */
    var Checkbox = function() {};
    _tool_.inherit(Checkbox, Radio);
    $.extend(Checkbox.prototype, {
        __initFieldComponent: function(options, __form, __row) {
            var _that = this,
                args = [].slice.call(arguments, 0),
                _data = options.data || [];;
            Radio.prototype.__initFieldComponent.apply(_that, args);
            //数组，配置多个
            if($.isArray(_data)) {
                $.each(_data, function(i, item) {
                    item.primary && _that._field.eq(i).attr('lay-skin', 'primary');
                    if(item['switch']) {
                        _that._field.eq(i).attr('lay-skin', 'switch');
                        item.text && _that._field.eq(i).attr('lay-text', item.text);
                    }
                });
            } else {
                _data.primary && _that._field.attr('lay-skin', 'primary');
                if(_data['switch']) {
                    _that._field.attr('lay-skin', 'switch');
                    _data.text && _that._field.attr('lay-text', item.text);
                }
            }
        },
        __fieldTmpl: '<input type="checkbox">'
    });
    _components_.checkbox = Checkbox;

    /**
     * 按钮组件 button，支持多个
     */
    var Button = function() {};
    _tool_.inherit(Button, AbstractComponent);
    $.extend(Button.prototype, {
        __initFieldComponent: function(options, __form, __row) {
            var _that = this,
                _fieldWrapper = $('<div></div>'),
                _size = options.size || 'block',
                _center = options.center,
                _data = options.data || [];
            processComponentSize(_fieldWrapper, _size);

            if(_center) {
                __row.css('text-align', 'center');
            }

            function buildButton(opts) {
                var _button = $(_that.__fieldTmpl);
                opts.submit && _button.attr('lay-submit', '');
                opts.submit && _button.attr('lay-filter', __form._filter);
                opts.theme && _button.addClass('layui-btn-' + opts.theme);
                opts.name && _button.attr('name', opts.name);
                opts.text && _button.text(opts.text);
                opts.attr && _button.attr(opts.attr);

                _fieldWrapper.append(_button);
            }
            //数组，配置多个
            if($.isArray(_data)) {
                $.each(_data, function(i, item) {
                    buildButton(item)
                });
            } else {
                buildButton(_data)
            }
            __row.append(_fieldWrapper);
        },
        __fieldTmpl: '<button class="layui-btn"></button>'
    });
    _components_.button = Button;

    var GForm = function(options) {
        this.__render(options);
    };
    GForm.fn = GForm.prototype;
    GForm.fn.getFilter = function() {
        return this._filter;
    };
    GForm.fn.__render = function(options) {
        if(this._initialized) {
            layui.hint().error('GForm已经初始化！');
            return;
        }
        options = options || {};

        //判断是否配置导航容器元素
        if(!!options.elem) {
            this._container = $(options.elem);
            //存在配置的容器
            if(!this._container.length) {
                this._container = undefined;
            }
        }
        if(!this._container) {
            //选取默认容器
            this._container = _body_.find('*[gene-form]');
        }

        if(!this._container || !this._container.length) {
            layui.hint().error('GForm没有正常初始化！');
            return;
        }
        this._container = this._container.eq(0);
        this._filter = options.filter || _tool_.makeHashCode('geneForm');

        var _that = this,
            _rows = options.rows || [],
            _elem = this._container,
            _callback = options.callback;

        _elem.empty();
        _elem.addClass('layui-form layui-fluid');
        _elem.attr('lay-filter', _that._filter);
        if(options.pane) {
            _elem.addClass('layui-form-pane');
        }

        function initComponent(options, __row) {
            var _comp = createComponent(options.type || 'text');
            if(_comp === false) {
                return false;
            }
            _comp.__init(options, _that, __row);
        }
        //遍历表单行
        $.each(_rows, function(i, _row) {
            var _div = $('<div class="layui-row layui-form-item"></div>');
            //一行中有多个组件
            if($.isArray(_row)) {
                $.each(_row, function(i, opts) {
                    initComponent(opts, _div);
                });
            } else {
                initComponent(_row, _div);
            }
            _elem.append(_div);
        });

        if(options.pane) {
            _elem.find('input[type="radio"],input[type="checkbox"]').parents('.layui-form-item').attr('pane', '');
        }
        _form_.render(null, _that._filter);

        _that._initialized = true;
        return _that;
    };

    /**
     * 处理表单序列化
     * @param {Function} fn
     * @param {JSON} options 附加选项 trimEmptyVal 是否移除空值 supportArrayName 支持数组名称
     */
    function processSerialize(fn, options) {
        if(typeof fn !== 'function') return;
        options = options || {};
        var nameIndex = {}, //数组name索引
            trimEmptyVal = !!options.trimEmptyVal, //移除值为空字符串的项
            supportArrayName = !!options.supportArrayName; //支持数组name，即：name[]会变成name[0]
        //此处this指的是GForm对象
        this._container.find('input,select,textarea').each(function() {
            //this指的是DOM
            var name = (this.name || '').replace(/^\s*|\s*&/, ''),
                value = this.value;

            if(!name) return;

            //用于支持数组 name
            if(supportArrayName) {
                if(/^.*\[\]$/.test(name)) {
                    var key = name.match(/^(.*)\[\]$/g)[0];
                    nameIndex[key] = nameIndex[key] | 0;
                    name = name.replace(/^(.*)\[\]$/, '$1[' + (nameIndex[key]++) + ']');
                }
            }

            if(/^checkbox|radio$/.test(this.type) && !this.checked) return;
            if(trimEmptyVal && value === '') return;
            fn(name, value);
        });
    }
    /**
     * 序列化表单，转换成key=value形式字符串
     * @param {JSON} options
     */
    GForm.fn.serialize = function(options) {
        return $.param(this.serializeArray(options));
    };
    /**
     * 序列化表单，转换成数组，数组每一项为：{name:name,value:value}
     * @param {JSON} options
     */
    GForm.fn.serializeArray = function(options) {
        var fieldArr = [];
        processSerialize.call(this, function(name, value) {
            fieldArr.push({
                name: name,
                value: value
            });
        }, options);
        return fieldArr;
    };
    /**
     * 序列化表单，转换为JSON对象，如果有同名输入项，则值为数组
     * @param {JSON} options
     */
    GForm.fn.serializeFieldValue = function(options) {
        var multivalue = _multivalue_.newMultiValue();
        processSerialize.call(this, function(name, value) {
            multivalue.put(name, value);
        }, options);
        return multivalue;
    };
    /**
     * 表单校验
     */
    GForm.fn.verify = function() {
        var verify = _form_.config.verify,
            stop = null,
            verifyElem = this._container.find('*[lay-verify]'), //获取需要校验的元素
            DANGER = 'layui-form-danger';
        layui.each(verifyElem, function(_, item) {
            var othis = $(this),
                vers = othis.attr('lay-verify').split('|'),
                verType = othis.attr('lay-verType') //提示方式
                ,
                value = othis.val();

            othis.removeClass(DANGER);
            layui.each(vers, function(_, thisVer) {
                var isTrue //是否命中校验
                    , errorText = '' //错误提示文本
                    ,
                    isFn = typeof verify[thisVer] === 'function';

                //匹配验证规则
                if(verify[thisVer]) {
                    var isTrue = isFn ? errorText = verify[thisVer](value, item) : !verify[thisVer][0].test(value);
                    errorText = errorText || verify[thisVer][1];

                    //如果是必填项或者非空命中校验，则阻止提交，弹出提示
                    if(isTrue) {
                        //提示层风格
                        if(verType === 'tips') {
                            layer.tips(errorText, function() {
                                if(typeof othis.attr('lay-ignore') !== 'string') {
                                    if(item.tagName.toLowerCase() === 'select' || /^checkbox|radio$/.test(item.type)) {
                                        return othis.next();
                                    }
                                }
                                return othis;
                            }(), {
                                tips: 1
                            });
                        } else if(verType === 'alert') {
                            layer.alert(errorText, {
                                title: '提示',
                                shadeClose: true
                            });
                        } else {
                            layer.msg(errorText, {
                                icon: 5,
                                shift: 6
                            });
                        }
                        if(!device.android && !device.ios) item.focus(); //非移动设备自动定位焦点
                        othis.addClass(DANGER);
                        return stop = true;
                    }
                }
            });
            if(stop) return stop;
        });
        return !!!stop;
    };
    /**
     * 表单值读写器
     */
    GForm.fn.val = function() {
        var _that = this,
            argLen = arguments.length;
        if(argLen == 0) {
            return undefined;
        } else if(argLen == 1) { //getter
            var _val = null;
            this._container.find('*[name="' + arguments[0] + '"]').each(function() {
                if(!$(this).is('input,select,textarea')) {
                    return;
                }
                //this指的是DOM
                var name = (this.name || '').replace(/^\s*|\s*&/, ''),
                    value = this.value;

                if(/^checkbox|radio$/.test(this.type) && !this.checked) return;
                if(_val === null) {
                    _val = value;
                } else if($.isArray(_val)) {
                    _val.push(value);
                } else {
                    _val = [_val, value];
                }
            });
        } else { //setter
            var _val = arguments[1],
                _select = false,
                _checkbox = false,
                _radio = false;
            this._container.find('*[name="' + arguments[0] + '"]').each(function(_index) {
                var _this = $(this);
                if(!_this.is('input,select,textarea')) {
                    return;
                }
                _this.val(_val);
                _select = _select || _this.is('select');
                _checkbox = _checkbox || (/^radio$/.test(this.type));
                _radio = _radio || (/^checkbox/.test(this.type));
            });
            _select && _form_.render('select', _that._filter);
            _checkbox && _form_.render('checkbox', _that._filter);
            _radio && _form_.render('radio', _that._filter);
        }
    };
    /**
     * 重置选择组件
     */
    GForm.fn.resetSelect = function(name, options, defVal) {
        if(!!!name) {
            return;
        }
        options = options || {};
        var _that = this,
            _select = this._container.find('*[name="' + name + '"]'),
            _text = options.text || 'text',
            _value = options.value || 'value',
            _data = options.data || [];

        options.placeholder && _field.attr('placeholder', options.placeholder);
        options.attr && _field.attr(options.attr);
        _select.empty();
        if(options.placeholder !== false) {
            $('<option></option>').val('').text(options.placeholder || '').appendTo(_select);
        }
        $.each(_data, function(i, item) {
            $('<option></option>').val(item[_value]).text(item[_text]).appendTo(_select);
        });
        if(defVal) {
            _select.val(defVal);
        }
        _form_.render('select', _that._filter);
    };

    function GFormExport() {}
    /**
     * 获取/设置组件
     */
    GFormExport.prototype.component = function() {
        var _that = this,
            argLen = arguments.length;
        if(argLen == 0) {
            return undefined;
        } else if(argLen == 1) { //getter
            return _components_[arguments[0]];
        } else { //setter
            if(typeof arguments[1] === 'function') {
                arguments[1].prototype.__init = arguments[1].prototype.__init || $.noop;
                _components_[arguments[0]] = arguments[1];
            } else {
                layui.hint().error('Unknown type: ' + type);
                return false;
            }

        }
    };
    GFormExport.prototype.render = function(options) {
        return new GForm(options);
    };
    exports('gform', new GFormExport());
});